﻿using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SoftwareConsulting.BI11.Controls.Properties;
using SoftwareConsulting.Interfaces.BasicEntities;
using SoftwareConsulting.BI11.Studio.BasicEntities;
using SoftwareConsulting.BI11.Studio.InternalServices;
using SoftwareConsulting.BI11.NumericalMethods.Geometry;

namespace SoftwareConsulting.BI11.Controls
{    
    /// <summary>
    /// Класс бокса взаимосвязей
    /// </summary>
    public partial class CorrelationsBox : UserControl
    {        
        private INamedHierarchy<CorrelationsEntity> _entities; //сущности
        private IHierarchy<CorrelationsLink> _links; //связи
        private Size _areaSize; //размер области
        private ContextMenuStrip _areaContext; //контекстное меню для области
        private ContextMenuStrip _entityContext; //контекстное меню для сущности
        private ContextMenuStrip _linkContext; //контекстное меню для связи
        private INamedHierarchy<CorrelationsEntity> _selectedEntities; //выделенные сущности
        private IHierarchy<CorrelationsLink> _selectedLinks; //выделенные связи
        private bool _generalizationsVisible; //флаг видимости связей "обобщение"
        private Brush _generalizationBrush; //кисть связи "обобщение"
        private bool _unionsVisible; //флаг видимости связей "объединение"
        private Brush _unionBrush; //кисть связи "объединение"
        private bool _compositionsVisible; //флаг видимости связей "композиция"
        private Brush _compositionBrush; //кисть связи "композиция"
        private bool _associationsVisible; //флаг видимости связей "ассоациация"
        private Dictionary<int,Brush> _associationBrushes; //кисти связи "ассоциация"
        private bool _entitiesVisible; //флаг видимости сущностей
        private Dictionary<int,Color> _entityBackColors; //цвета сущности
        private Color _entityForeColor; //цвет рамки сущности
        private Color _selectedEntityBackColor; //цвет выделенной сущности
        private Color _selectedEntityForeColor; //цвет рамки выделенной сущности
        private Color _hoveredEntityBackColor; //цвет подсвеченной сущности
        private Color _hoveredEntityForeColor; //цвет рамки подсвеченной сущности
        private Color _hoveredEntityHoverColor; //цвет задней поверхности подсвеченной сущности
        private Brush _drawingLinkBrush; //кисть проводимой связи
        private bool _lastSelectedEntityClew; //флаг проведения линии от последней выделенной сущности
        private bool _lastSelectedEntityClewActive; //флаг рабочего состояния лини от последней выделенной сущности
        private Point _currentMousePosition;
        private Size _previousSize;

        public event ClickArea AreaClick;
        public event OpeningEntityContextMenu EntityContextMenuOpening;
        public event OpeningLinkContextMenu LinkContextMenuOpening;
        public event SelectedEntity EntitySelected;
        public event SelectedLink LinkSelected;

        /// <summary>
        /// Сбрасывает все свойства компонента в установленные по умолчанию
        /// </summary>
        private void SetDefaults()
        {
            _entities = new NamedHierarchy<CorrelationsEntity>();
            _links = new Hierarchy<CorrelationsLink>();
            _areaSize = new Size(1000, 1000);
            _previousSize = this.Size;
            _areaContext = null;
            _entityContext = null;
            _linkContext = null;
            _selectedEntities = new NamedHierarchy<CorrelationsEntity>();
            _selectedLinks = new Hierarchy<CorrelationsLink>();
            _generalizationsVisible = true;
            _generalizationBrush = SystemBrushes.MenuHighlight;
            _unionsVisible = true;
            _unionBrush = SystemBrushes.WindowFrame;
            _associationsVisible = true;
            _associationBrushes = new Dictionary<int, Brush>();
            _compositionsVisible = true;
            _compositionBrush = SystemBrushes.MenuHighlight;            
            _drawingLinkBrush = SystemBrushes.MenuHighlight;
            _entitiesVisible = true;
            _entityBackColors = new Dictionary<int, Color>();
            _entityForeColor = SystemColors.InactiveCaptionText;
            _selectedEntityBackColor = SystemColors.MenuHighlight;
            _selectedEntityForeColor = SystemColors.MenuText;
            _hoveredEntityBackColor = SystemColors.ActiveCaption;
            _hoveredEntityForeColor = SystemColors.ActiveCaptionText;
            _hoveredEntityHoverColor = SystemColors.Highlight;
            _lastSelectedEntityClew = false;
            _lastSelectedEntityClewActive = false;
            _currentMousePosition = new Point(0, 0);
            Refresh();
        }

        /// <summary>
        /// Возвращает/устанавливает флаг проведения линии от последней выделенной сущности
        /// </summary>
        public bool LastSelectedEntityClew
        {
            get
            {
                return _lastSelectedEntityClew;
            }
            set
            {
                _lastSelectedEntityClew = value;
                _lastSelectedEntityClewActive = false;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает кисть проводимой
        /// </summary>
        public Brush DrawingLinkBrush
        {
            get
            {
                return _drawingLinkBrush;
            }
            set
            {
                _drawingLinkBrush = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает цвет рамки сущности
        /// </summary>
        public Color EntityForeColor
        {
            get
            {
                return _entityForeColor;
            }
            set
            {
                _entityForeColor = value;
                foreach (CorrelationsEntity entity in _entities)
                {
                    entity.SetForeColor(value);
                }
            }
        }

        /// <summary>
        /// Возвращает/устанавливает цвет выделенной сущности
        /// </summary>
        public Color SelectedEntityBackColor
        {
            get
            {
                return _selectedEntityBackColor;
            }
            set
            {
                _selectedEntityBackColor = value;
                foreach (CorrelationsEntity entity in _entities)
                {
                    entity.SetSelectedBackColor(value);
                }
            }
        }

        /// <summary>
        /// Возвращает/устанавливает цвет рамки выделенной сущности
        /// </summary>
        public Color SelectedEntityForeColor
        {
            get
            {
                return _selectedEntityForeColor;
            }
            set
            {
                _selectedEntityForeColor = value;
                foreach (CorrelationsEntity entity in _entities)
                {
                    entity.SetSelectedForeColor(value);
                }
            }
        }

        /// <summary>
        /// Возвращает/устанавливает цвет подсвеченной сущности
        /// </summary>
        public Color HoveredEntityBackColor
        {
            get
            {
                return _hoveredEntityBackColor;
            }
            set
            {
                _hoveredEntityBackColor = value;
                foreach (CorrelationsEntity entity in _entities)
                {
                    entity.SetHoveredBackColor(value);
                }
            }
        }

        /// <summary>
        /// Возвращает/устанавливает цвет рамки подсвеченной сущности
        /// </summary>
        public Color HoveredEntityForeColor
        {
            get
            {
                return _hoveredEntityForeColor;
            }
            set
            {
                _hoveredEntityForeColor = value;
                foreach (CorrelationsEntity entity in _entities)
                {
                    entity.SetHoveredForeColor(value);
                }
            }
        }

        /// <summary>
        /// Возвращает/устанавливает цвет задней поверхности подсвеченной сущности
        /// </summary>
        public Color HoveredEntityHoverColor
        {
            get
            {
                return _hoveredEntityHoverColor;
            }
            set
            {
                _hoveredEntityHoverColor = value;
                foreach (CorrelationsEntity entity in _entities)
                {
                    entity.SetHoveredHoverColor(value);
                }
            }
        }

        /// <summary>
        /// Возвращает/устанавливает кисть связи "обобщение"
        /// </summary>
        public Brush GeneralizationBrush
        {
            get
            {
                return _generalizationBrush;
            }
            set
            {
                _generalizationBrush = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает кисть связи "объединение"
        /// </summary>
        public Brush UnionBrush
        {
            get
            {
                return _unionBrush;
            }
            set
            {
                _unionBrush = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает кисть связи "композиция"
        /// </summary>
        public Brush CompositionBrush
        {
            get
            {
                return _compositionBrush;
            }
            set
            {
                _compositionBrush = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает кисти связи "ассоциация"
        /// </summary>
        public Dictionary<int,Brush> AssociationBrushes
        {
            get
            {
                return _associationBrushes;
            }            
        }

        /// <summary>
        /// Устанавливает кисти связи "ассоциация"
        /// </summary>
        public void SetAssociationBrushes(int associationGroup, Brush brush)
        {
            if (_associationBrushes.ContainsKey(associationGroup))
            {
                _associationBrushes[associationGroup] = brush;
            }
            else
            {
                _associationBrushes.Add(associationGroup, brush);
            }
            Refresh();
        }

        /// <summary>
        /// Возвращает/устанавливает флаг видимости связей "обобщение"
        /// </summary>
        public bool GeneralizationsVisible
        {
            get
            {
                return _generalizationsVisible;
            }
            set
            {
                _generalizationsVisible = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает флаг видимости связей "объединение"
        /// </summary>
        public bool UnionsVisible
        {
            get
            {
                return _unionsVisible;
            }
            set
            {
                _unionsVisible = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает флаг видимости связей "композиция"
        /// </summary>
        public bool CompositionsVisible
        {
            get
            {
                return _compositionsVisible;
            }
            set
            {
                _compositionsVisible = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает флаг видимости связей "ассоциация"
        /// </summary>
        public bool AssociationsVisible
        {
            get
            {
                return _associationsVisible;
            }
            set
            {
                _associationsVisible = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает/устанавливает флаг видимости сущностей
        /// </summary>
        public bool EntitiesVisible
        {
            get
            {
                return _entitiesVisible;
            }
            set
            {
                _entitiesVisible = value;
                Refresh();
            }
        }

        /// <summary>
        /// Возвращает цвета сущности
        /// </summary>
        public Dictionary<int, Color> EntityBackColors
        {
            get
            {
                return _entityBackColors;
            }
        }

        /// <summary>
        /// Устанавливает цвета сущности
        /// </summary>
        public void SetEntityBackColors(int entityGroup, Color color)
        {
            if (_entityBackColors.ContainsKey(entityGroup))
            {
                _entityBackColors[entityGroup] = color;
            }
            else
            {
                _entityBackColors.Add(entityGroup, color);
            }
            foreach (CorrelationsEntity entity in _entities)
            {
                entity.SetBackColor(_entityBackColors[entity.Group]);
            }
        }

        /// <summary>
        /// Вызывает контекстное меню для указанной сущности
        /// </summary>
        private void RollDownEntityContextMenu(CorrelationsEntity entity, Point controlLocation)
        {
            SelectEntity(entity);
            EntityContextMenuOpening(entity);
            _entityContext.Show(this, controlLocation);
        }

        /// <summary>
        /// Вызывает контекстное меню для указанной связи
        /// </summary>
        private void RollDownLinkContextMenu(CorrelationsLink link, Point controlLocation)
        {
            SelectLink(link);
            LinkContextMenuOpening(link);
            _linkContext.Show(this, controlLocation);
        }

        /// <summary>
        /// Вызывает контекстное меню для указанной сущности
        /// </summary>
        private void RollDownEntityContextMenu(CorrelationsEntity entity)
        {
            RollDownEntityContextMenu(entity, GetControlLocation(entity.EntityLocation));            
        }

        /// <summary>
        /// Вызывает контекстное меню для указанной связи
        /// </summary>
        private void RollDownLinkContextMenu(CorrelationsLink link)
        {
            RollDownLinkContextMenu(link, GetControlLocation(link.Source.EntityLocation));
        }

        private void OneEntitySelected(CorrelationsEntity entity, bool multiselectAdd)
        {
            if (!multiselectAdd)
            {
                SelectEntity(entity);
            }
            else
            {
                _selectedEntities.Add(entity);
                SelectEntity(_selectedEntities);
            }
        }

        /// <summary>
        /// Выделяет указанную сущность, снимая выделение с других сущностей
        /// </summary>
        /// <param name="entity"></param>
        public void SelectEntity(CorrelationsEntity entity)
        {
            _selectedEntities = new NamedHierarchy<CorrelationsEntity>();
            _selectedEntities.Add(entity);
            foreach (CorrelationsEntity otherEntity in _entities)
            {
                if (otherEntity != entity)
                    otherEntity.Selected = false;
            }
            entity.Selected = true;
            if (_lastSelectedEntityClew)
            {
                _lastSelectedEntityClewActive = !_lastSelectedEntityClewActive;
            }            
            Refresh();
            if (EntitySelected != null)
            {
                EntitySelected(entity, false);
            }
        }

        /// <summary>
        /// Выделяет указанные сущности, снимая выделение с других сущностей
        /// </summary>
        /// <param name="entities"></param>
        public void SelectEntity(INamedHierarchy<CorrelationsEntity> entities)
        {
            _selectedEntities = entities;
            Refresh();
            foreach (CorrelationsEntity anyentity in _entities)
            {
                if (entities.Contains(anyentity))
                {
                    anyentity.Selected = true;
                    if (EntitySelected != null)
                    {
                        EntitySelected(anyentity, false);
                    }
                }
                else
                {
                    anyentity.Selected = false;
                }
            }
        }        

        /// <summary>
        /// Выделяет указанную связь, снимая выделение с других связей
        /// </summary>
        /// <param name="link"></param>
        public void SelectLink(CorrelationsLink link)
        {
            _selectedLinks = new Hierarchy<CorrelationsLink>();
            _selectedLinks.Add(link);            
            Refresh();
            if (LinkSelected != null)
            {
                LinkSelected(link);
            }
        }        

        /// <summary>
        /// Выделяет указанные связи, снимая выделение с других связей
        /// </summary>
        /// <param name="links"></param>
        public void SelectLink(Hierarchy<CorrelationsLink> links)
        {
            _selectedLinks = links;
            Refresh();
            if (LinkSelected != null)
            {
                foreach (CorrelationsLink link in links)
                {
                    LinkSelected(link);
                }
            }
        }

        /// <summary>
        /// Возвращает/устанавливает контекстное меню для области
        /// </summary>
        public ContextMenuStrip AreaContextMenu
        {
            get
            {
                return _areaContext;
            }
            set
            {
                _areaContext = value;
                ContextMenuStrip = _areaContext;
            }
        }

        /// <summary>
        /// Возвращает/устанавливает контекстное меню для сущности
        /// </summary>
        public ContextMenuStrip EntityContextMenu
        {
            get
            {
                return _entityContext;
            }
            set
            {
                _entityContext = value;                
            }
        }

        /// <summary>
        /// Возвращает/устанавливает контекстное меню для связи
        /// </summary>
        public ContextMenuStrip LinkContextMenu
        {
            get
            {
                return _linkContext;
            }
            set
            {
                _linkContext = value;
            }
        }

        /// <summary>
        /// Возвращает список сущностей
        /// </summary>
        /// <returns></returns>
        public IEnumerable<CorrelationsEntity> GetEntitiesList()
        {            
            foreach (CorrelationsEntity entity in _entities)
            {
                yield return entity;
            }            
        }

        /// <summary>
        /// Возвращает список связей
        /// </summary>
        /// <returns></returns>
        public IEnumerable<CorrelationsLink> GetLinksList()
        {            
            foreach (CorrelationsLink link in _links)
            {
                yield return link;
            }
        }

        /// <summary>
        /// Добавляет сущность
        /// </summary>
        /// <param name="entityName"></param>
        /// <param name="entityLocation"></param>
        /// <param name="entityGroup"></param>
        public void AddEntity(string entityName, Point entityLocation, int entityGroup)
        {
            if ((entityLocation.X > _areaSize.Width) || (entityLocation.X < 0) || (entityLocation.Y > _areaSize.Height) || (entityLocation.Y < 0))
                throw new IndexOutOfRangeException(Resources.BI_EXCEPTION_POINTOUTOFAREA);
            CorrelationsEntity newEntity = new CorrelationsEntity(this, entityName, entityLocation, entityGroup);
            _entities.Add(newEntity);
            SelectEntity(newEntity);
            newEntity.EntitySelected += OneEntitySelected;
        }

        /// <summary>
        /// Добавляет связь
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="kind"></param>
        public void AddLink(CorrelationsEntity source, CorrelationsEntity destination, CorrelationsLinkKind kind)
        {
            Assert.FailIf(kind == CorrelationsLinkKind.Association, Assert.Id.Assert00017);//надо другой перегруженный метод использовать
            Assert.FailIfNot(_entities.Contains(source), Assert.Id.Assert00018);
            Assert.FailIfNot(_entities.Contains(destination), Assert.Id.Assert00019);            
            _links.Add(new CorrelationsLink(source, destination, kind));            
        }

        /// <summary>
        /// Добавляет связь-ассоциацию
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="kind"></param>
        /// <param name="assocationGroup"></param>
        public void AddLink(CorrelationsEntity source, CorrelationsEntity destination, CorrelationsLinkKind kind, int assocationGroup)
        {
            Assert.FailIfNot(kind == CorrelationsLinkKind.Association, Assert.Id.Assert00039);//надо другой перегруженный метод использовать
            Assert.FailIfNot(_entities.Contains(source), Assert.Id.Assert00040);
            Assert.FailIfNot(_entities.Contains(destination), Assert.Id.Assert00041);
            _links.Add(new CorrelationsLink(source, destination, kind, assocationGroup));
        }

        /// <summary>
        /// Добавляет связь
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destinations"></param>
        /// <param name="kind"></param>
        public void AddLink(CorrelationsEntity source, List<CorrelationsEntity> destinations, CorrelationsLinkKind kind)
        {
            Assert.FailIf(kind == CorrelationsLinkKind.Association, Assert.Id.Assert00042);//надо другой перегруженный метод использовать
            Assert.FailIf((kind == CorrelationsLinkKind.Generalization) && (destinations.Count > 1), Assert.Id.Assert00043);
            Assert.FailIfNot(_entities.Contains(source), Assert.Id.Assert00044);
            foreach (CorrelationsEntity entity in destinations)
            {
                Assert.FailIfNot(_entities.Contains(entity), Assert.Id.Assert00045);
            }
            _links.Add(new CorrelationsLink(source, destinations, kind));
        }

        /// <summary>
        /// Добавляет связь-ассоциацию
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destinations"></param>
        /// <param name="kind"></param>
        /// <param name="associationGroup"></param>
        public void AddLink(CorrelationsEntity source, List<CorrelationsEntity> destinations, CorrelationsLinkKind kind, int associationGroup)
        {
            Assert.FailIfNot(kind == CorrelationsLinkKind.Association, Assert.Id.Assert00046);//надо другой перегруженный метод использовать
            Assert.FailIf((kind == CorrelationsLinkKind.Generalization) && (destinations.Count > 1), Assert.Id.Assert00047);
            Assert.FailIfNot(_entities.Contains(source), Assert.Id.Assert00048);
            foreach (CorrelationsEntity entity in destinations)
            {
                Assert.FailIfNot(_entities.Contains(entity), Assert.Id.Assert00049);
            }
            _links.Add(new CorrelationsLink(source, destinations, kind, associationGroup));
        }

        /// <summary>
        /// Возвращает/устанавливает размер области
        /// </summary>
        public Size AreaSize
        {
            get
            {
                return _areaSize;
            }
            set
            {
                _areaSize = value;             
            }
        }        

        /// <summary>
        /// Перерисовывает бокс
        /// </summary>
        private void Repaint(Graphics g)
        {                       
            if (_entities.ItemsCount == 0)
                return; //нет сущностей, значит нет и связей - рисовать нечего              
            //рисуем линии
            Point p1 = new Point();
            Point p2 = new Point();
            Brush br = _generalizationBrush;
            Pen p = new Pen(br);
            p.EndCap = LineCap.NoAnchor;
            p.Alignment = PenAlignment.Center;
            foreach (CorrelationsLink link in _links)
            {
                switch(link.Kind)
                {
                    case CorrelationsLinkKind.Generalization:
                        if (!_generalizationsVisible)
                            continue;
                        break;
                    case CorrelationsLinkKind.Union:
                        if (!_unionsVisible)
                            continue;
                        break;
                    case CorrelationsLinkKind.Composition:
                        if (!_compositionsVisible)
                            continue;
                        break;
                    case CorrelationsLinkKind.Association:
                        if (!_associationsVisible)
                            continue;
                        break;
                    default:
                        Assert.Fail(Assert.Id.Assert00102);
                        break;
                }
                CorrelationsEntity sourceEntity = link.Source;
                List<CorrelationsEntity> destinationEntities = link.GetDestinations();
                link.CalculatePoints();
                p1 = GetControlLocation(link.GetSourcePoint());
                p.Width = _selectedLinks.Contains(link) ? 4 : 2;
                foreach (Point destPoint in link.GetDestinationPoints())
                {
                    p2 = GetControlLocation(destPoint);
                    switch (link.Kind)
                    {
                        case CorrelationsLinkKind.Generalization:
                            p.Brush = _generalizationBrush;
                            p.DashStyle = DashStyle.Solid;
                            break;
                        case CorrelationsLinkKind.Union:                            
                            p.Brush = _unionBrush;
                            p.DashStyle = DashStyle.Dot;
                            break;
                        case CorrelationsLinkKind.Composition:
                            p.Brush = _compositionBrush;
                            p.DashStyle = DashStyle.Solid;
                            break;
                        case CorrelationsLinkKind.Association:
                            p.Brush = _associationBrushes[link.AssociationGroup];
                            p.DashStyle = DashStyle.DashDot;
                            break;
                        default:
                            Assert.Fail(Assert.Id.Assert00054);
                            break;
                    }
                    Rectangle rect = GeometricMethods.TwoPointsToRectangle(p1, p2);
                    if ((rect.Width == 0) && (g.IsVisible(p1) || g.IsVisible(p2))
                        || g.IsVisible(rect))
                    {
                        g.DrawLine(p, p1, p2);
                    }
                    if ((rect.Width == 0) || g.IsVisible(p2))
                    {
                        //TODO рисовать стрелочку                    
                    }
                }                                
            }
            if (_lastSelectedEntityClew && _lastSelectedEntityClewActive)
            {
                p.Brush = _drawingLinkBrush;
                p.DashStyle = DashStyle.Solid;
                p.Width = 2;
                p1 = _selectedEntities.GetItemByPosition(_selectedEntities.ItemsCount - 1).EntityLocation;
                g.DrawLine(p, p1, _currentMousePosition);
            }                       
        }        

        /// <summary>
        /// Возвращает положение в области по известному положению на боксе
        /// </summary>
        /// <param name="controlLocation"></param>
        /// <returns></returns>
        private Point GetAreaLocation(Point controlLocation)
        {
            int X = Width == 0 ? 0 : Convert.ToInt32(Math.Round((double)_areaSize.Width / (double)Width * controlLocation.X));
            int Y = Height == 0 ? 0 : Convert.ToInt32(Math.Round((double)_areaSize.Height / (double)Height * controlLocation.Y));
            return new Point(X, Y);
        }

        /// <summary>
        /// Возвращает положение в боксе по известному положению в области
        /// </summary>
        /// <param name="areaLocation"></param>
        /// <returns></returns>
        public Point GetControlLocation(Point areaLocation)
        {
            int X = _areaSize.Width == 0 ? 0 : Convert.ToInt32(Math.Round((double)Width / (double)_areaSize.Width * areaLocation.X));
            int Y = _areaSize.Height == 0 ? 0 : Convert.ToInt32(Math.Round((double)Height / (double)_areaSize.Height * areaLocation.Y));
            return new Point(X, Y);
        }

        /// <summary>
        /// Масштабирует бокс
        /// </summary>
        public void Scale()
        {
            double xScale = (double)this.Width / (double)_previousSize.Width;
            double yScale = (double)this.Height / (double)_previousSize.Height;
            foreach (CorrelationsEntity entity in _entities)
            {
                entity.Scale(xScale, yScale);
            }
            _previousSize=this.Size;
            Refresh();
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        public CorrelationsBox()
        {
            InitializeComponent();
            SetDefaults();
        }

        private void CorrelationsBox_Paint(object sender, PaintEventArgs e)
        {
            Repaint(e.Graphics);
        }

        private void CorrelationsBox_MouseClick(object sender, MouseEventArgs e)
        {
            if (AreaClick != null)
            {
                AreaClick(e.Button, GetAreaLocation(e.Location));
            }
        }

        private void CorrelationsBox_MouseMove(object sender, MouseEventArgs e)
        {
            _currentMousePosition = e.Location;
        }

        private void CorrelationsBox_SizeChanged(object sender, EventArgs e)
        {
            Scale();
        }
    }

    /// <summary>
    /// Класс сущности взаимосвязей
    /// </summary>
    public class CorrelationsEntity:Named, ICloneable
    {        
        private CorrelationsBox _area;
        private Point _areaLocation;
        private BlockingTextbox _textBox;        
        private int _group;

        public event SelectedEntity EntitySelected;

        /// <summary>
        /// Создает клон сущности
        /// </summary>
        public object Clone()
        {
            CorrelationsEntity entity = new CorrelationsEntity(_area, Name, _areaLocation, _group);
            entity.SetId(Id);
            return entity;
        }

        public void OneEntitySelected(bool multiselectAdd)
        {
            if (EntitySelected != null)
            {
                EntitySelected(this, multiselectAdd);
            }
        }

        /// <summary>
        /// Возвращает/устанавливает флаг выделения
        /// </summary>
        public bool Selected
        {
            get
            {
                return _textBox.Selected;
            }
            set
            {
                _textBox.Selected = value;
            }
        }

        /// <summary>
        /// Возвращает/устанавливает группу сущности
        /// </summary>
        public int Group
        {
            get
            {
                return _group;
            }
            set
            {
                _group = value;
            }
        }

        /// <summary>
        /// Возвращает размер текстбокса сущности
        /// </summary>
        public Size ControlSize
        {
            get
            {
                return _textBox.Size;
            }
        }

        /// <summary>
        /// Возвращает размер сущности
        /// </summary>
        public Size EntitySize
        {
            get
            {
                return _textBox.FormalSize;
            }
        }

        /// <summary>
        /// Устанавливает цвет фона сущности
        /// </summary>
        /// <param name="color"></param>
        public void SetBackColor(Color color)
        {
            _textBox.SetBackColor(color);
        }

        /// <summary>
        /// Устанавливает цвет рамки сущности
        /// </summary>
        /// <param name="color"></param>
        public void SetForeColor(Color color)
        {
            _textBox.SetForeColor(color);
        }

        /// <summary>
        /// Устанавливает цвет фона выделенной сущности
        /// </summary>
        /// <param name="color"></param>
        public void SetSelectedBackColor(Color color)
        {
            _textBox.SetSelectedBackColor(color);
        }

        /// <summary>
        /// Устанавливает цвет рамки выделенной сущности
        /// </summary>
        /// <param name="color"></param>
        public void SetSelectedForeColor(Color color)
        {
            _textBox.SetSelectedForeColor(color);
        }

        /// <summary>
        /// Устанавливает цвет фона подсвеченной сущности
        /// </summary>
        /// <param name="color"></param>
        public void SetHoveredBackColor(Color color)
        {
            _textBox.SetHoveredBackColor(color);
        }

        /// <summary>
        /// Устанавливает цвет рамки подсвеченной сущности
        /// </summary>
        /// <param name="color"></param>
        public void SetHoveredForeColor(Color color)
        {
            _textBox.SetHoveredForeColor(color);
        }

        /// <summary>
        /// Устанавливает цвет задней поверхности подсвеченной сущности
        /// </summary>
        /// <param name="color"></param>
        public void SetHoveredHoverColor(Color color)
        {
            _textBox.SetHoveredHoverColor(color);
        }

        /// <summary>
        /// Масштабирует текстбокс сущности
        /// </summary>        
        public void Scale(double xScaleCoefficient, double yScaleCoefficient)
        {
            _textBox.Height = Convert.ToInt32(Math.Round(_textBox.Height * yScaleCoefficient));
            _textBox.Width = Convert.ToInt32(Math.Round(_textBox.Width * xScaleCoefficient));
            double realSquare = _textBox.Width * _textBox.Height;
            double prefSquare = _textBox.PreferredSize.Width * _textBox.PreferredSize.Height;
            float newFontSize = float.Parse((realSquare / prefSquare * _textBox.Font.Size).ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
            _textBox.Font = new Font(_textBox.Font.FontFamily, newFontSize);
        }

        /// <summary>
        /// Устанавливает имя сущности
        /// </summary>
        /// <param name="newName"></param>
        public override void SetName(string newName)
        {
            base.SetName(newName);
            _textBox.Text = Name;
        }

        /// <summary>
        /// Возвращает положение текстбокса сущности
        /// </summary>
        public Point ControlLocation
        {
            get
            {
                return _textBox.Location;
            }
        }
        
        /// <summary>
        /// Возвращает/устанавливает положение сущности
        /// </summary>
        public Point EntityLocation
        {
            get
            {
                return _areaLocation;
            }
            set
            {
                _areaLocation = value;
                _textBox.Location = _area.GetControlLocation(_areaLocation);
            }
        }        

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="newName"></param>
        /// <param name="newLocation"></param>        
        public CorrelationsEntity(CorrelationsBox area, string newName, Point newLocation, int newGroup):
            base()
        {
            _area = area;
            _textBox = new BlockingTextbox();
            _textBox.Visible = false;
            _textBox.Parent = _area;
            _textBox.BoxSelected += new SelectedBox(OneEntitySelected);
            EntityLocation = newLocation;
            SetName(newName);
            _textBox.Visible = true;
            _group = newGroup;
        }
    }

    /// <summary>
    /// Класс связи между сущностями
    /// </summary>
    public class CorrelationsLink:UniqueChild
    {        
        private CorrelationsEntity _source;
        private List<CorrelationsEntity> _destinations;
        private CorrelationsLinkKind _kind;
        private int _associationGroup;
        private Point _sourcePoint;
        private List<Point> _destinationPoints;

        /// <summary>
        /// Возвращает исходную точку
        /// </summary>
        /// <returns></returns>
        public Point GetSourcePoint()
        {
            return new Point(_sourcePoint.X, _sourcePoint.Y);
        }

        /// <summary>
        /// Возвращает целевые точки
        /// </summary>
        /// <returns></returns>
        public List<Point> GetDestinationPoints()
        {
            List<Point> result = new List<Point>();
            for (int i = 0; i < _destinationPoints.Count; i++)
            {
                result.Add(new Point(_destinationPoints[i].X, _destinationPoints[i].Y));
            }
            return result;
        }

        /// <summary>
        /// Вычисляет положение исходной и целевых точек (в координатах области)
        /// </summary>
        public void CalculatePoints()
        {            
            //рассчитываем сторону исходной точки
            int lefts = 0;
            int rights = 0;
            int tops = 0;
            int bottoms = 0;
            int max = -1;            
            Side sourceSide = Side.None;
            for (int i = 0; i < _destinations.Count; i++)            
            {
                CorrelationsEntity destination = _destinations[i];                
                int distanceY = _source.EntityLocation.Y - destination.EntityLocation.Y;
                int distanceX = _source.EntityLocation.X - destination.EntityLocation.X;
                if (Math.Abs(distanceY) >= Math.Abs(distanceX))
                {
                    if (_source.EntityLocation.Y >= destination.EntityLocation.Y)
                    {
                        tops++;
                        if (tops > max)
                        {
                            max = tops;
                            sourceSide = Side.Top;
                        }
                    }
                    else
                    {
                        bottoms++;
                        if (bottoms > max)
                        {
                            max = bottoms;
                            sourceSide = Side.Bottom;
                        }
                    }
                }
                else
                {
                    if (_source.EntityLocation.X <= destination.EntityLocation.X)
                    {
                        rights++;
                        if (rights > max)
                        {
                            max = rights;
                            sourceSide = Side.Right;
                        }
                    }
                    else
                    {
                        lefts++;
                        if (lefts > max)
                        {
                            max = lefts;
                            sourceSide = Side.Left;
                        }
                    }
                }                
            }
            //рассчитываем исходную точку
            switch(sourceSide)
            {
                case Side.Left:
                    _sourcePoint = _source.EntityLocation;
                    _sourcePoint.Offset(0, _source.EntitySize.Height / 2);
                    break;
                case Side.Top:
                    _sourcePoint = _source.EntityLocation;
                    _sourcePoint.Offset(_source.EntitySize.Width / 2, 0);
                    break;
                case Side.Right:
                    _sourcePoint = _source.EntityLocation;
                    _sourcePoint.Offset(_source.EntitySize.Width, _source.EntitySize.Height / 2);
                    break;
                case Side.Bottom:
                    _sourcePoint = _source.EntityLocation;
                    _sourcePoint.Offset(_source.EntitySize.Width / 2, _source.EntitySize.Height);
                    break;
                default:
                    Assert.Fail(Assert.Id.Assert00050);
                    break;
            }
            //рассчитываем точки назначения
            _destinationPoints = new List<Point>();
            for (int i = 0; i < _destinations.Count; i++)
            {
                CorrelationsEntity destination = _destinations[i];
                int distanceY = _sourcePoint.Y - destination.EntityLocation.Y;
                int distanceX = _sourcePoint.X - destination.EntityLocation.X;
                Side destinationSide = Side.None;
                if (Math.Abs(distanceY) >= Math.Abs(distanceX))
                {
                    if (_sourcePoint.Y >= destination.EntityLocation.Y)
                    {
                        destinationSide = Side.Top;
                    }
                    else
                    {
                        destinationSide = Side.Bottom;
                    }
                }
                else
                {
                    if (_sourcePoint.X <= destination.EntityLocation.X)
                    {
                        destinationSide = Side.Right;
                    }
                    else
                    {
                        destinationSide = Side.Left;
                    }
                }
                Point destinationPoint = new Point();
                switch (destinationSide)
                {
                    case Side.Left:
                        destinationPoint = destination.EntityLocation;
                        destinationPoint.Offset(0, destination.EntitySize.Height / 2);
                        break;
                    case Side.Top:
                        destinationPoint = destination.EntityLocation;
                        destinationPoint.Offset(destination.EntitySize.Width / 2, 0);
                        break;
                    case Side.Right:
                        destinationPoint = destination.EntityLocation;
                        destinationPoint.Offset(destination.EntitySize.Width, destination.EntitySize.Height / 2);
                        break;
                    case Side.Bottom:
                        destinationPoint = destination.EntityLocation;
                        destinationPoint.Offset(destination.EntitySize.Width / 2, destination.EntitySize.Height);
                        break;
                    default:
                        Assert.Fail(Assert.Id.Assert00103);
                        break;
                }
                _destinationPoints.Add(destinationPoint);
            }
        }

        /// <summary>
        /// Возвращает вид связи
        /// </summary>
        public CorrelationsLinkKind Kind
        {
            get
            {
                return _kind;
            }
        }

        /// <summary>
        /// Возвращает числовое значение вида связи-ассоциации
        /// </summary>
        public int AssociationGroup
        {
            get
            {
                return _associationGroup;
            }
        }

        /// <summary>
        /// Вовзвращает источник связи
        /// </summary>
        public CorrelationsEntity Source
        {
            get
            {
                return _source;
            }
        }

        /// <summary>
        /// Возвращает назначения связи
        /// </summary>
        /// <returns></returns>
        public List<CorrelationsEntity> GetDestinations()
        {
            List<CorrelationsEntity> result = new List<CorrelationsEntity>();
            foreach (CorrelationsEntity entity in _destinations)
            {
                result.Add(entity);
            }
            return result;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <parem name="kind"></parem>        
        public CorrelationsLink(CorrelationsEntity source, CorrelationsEntity destination, CorrelationsLinkKind kind)
            : base()
        {
            Assert.FailIf(_kind == CorrelationsLinkKind.Association, Assert.Id.Assert00104);//надо использовать другой конструктор
            _source = source;
            _destinations = new List<CorrelationsEntity>();
            _destinations.Add(destination);
            _kind = kind;            
        }

        /// <summary>
        /// Конструктор класса связи-ассоциации
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="kind"></param>
        /// <param name="associationGroup"></param>
        public CorrelationsLink(CorrelationsEntity source, CorrelationsEntity destination, CorrelationsLinkKind kind, int associationGroup)
            : base()
        {
            Assert.FailIfNot(_kind == CorrelationsLinkKind.Association, Assert.Id.Assert00055);//надо использовать другой конструктор
            _source = source;
            _destinations = new List<CorrelationsEntity>();
            _destinations.Add(destination);
            _kind = kind;            
            _associationGroup = associationGroup;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destinations"></param>
        /// <param name="kind"></param>
        public CorrelationsLink(CorrelationsEntity source, List<CorrelationsEntity> destinations, CorrelationsLinkKind kind)
            : base()
        {
            Assert.FailIf(_kind == CorrelationsLinkKind.Association, Assert.Id.Assert00105);//надо использовать другой конструктор
            _source = source;
            _destinations = destinations;
            _kind = kind;
        }

        /// <summary>
        /// Конструктор класса связи-ассоциации
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destinations"></param>
        /// <param name="kind"></param>
        /// <param name="associationGroup"></param>
        public CorrelationsLink(CorrelationsEntity source, List<CorrelationsEntity> destinations, CorrelationsLinkKind kind, int associationGroup)
            : base()
        {
            Assert.FailIfNot(_kind == CorrelationsLinkKind.Association, Assert.Id.Assert00056);//надо использовать другой конструктор
            _source = source;
            _destinations = destinations;
            _kind = kind;
            _associationGroup = associationGroup;
        }
    }

    public delegate void ClickArea(MouseButtons button, Point areaPoint);
    public delegate void OpeningEntityContextMenu(CorrelationsEntity entity);
    public delegate void OpeningLinkContextMenu(CorrelationsLink link);
    public delegate void SelectedEntity(CorrelationsEntity entity, bool multiselectAdd);
    public delegate void SelectedLink(CorrelationsLink link);
}
